#!/usr/bin/perl
use XML::Simple qw(:strict);
use Data::Dumper;
my $inputFile = '../redditApi.xml';
my $outputFile = 'RedditApi/RedditApiBase.php';
my $doc = XMLin($inputFile, KeyAttr => 0, ForceArray => 1);
my $debug = shift;

print Dumper($doc) if $debug;

open OUTPUT, ">$outputFile";
print OUTPUT CreateRedditApi($doc);
close OUTPUT;

sub ApiNameToClassName
{
    my $name = shift;
    return sprintf("%s%sApi", uc(substr($name, 0, 1)), substr($name, 1));
}
sub ApiNameToVarName
{
    my $name = shift;
    return sprintf("%s%s", uc(substr($name, 0, 1)), substr($name, 1));
}
sub AddTab
{
    return [map { "\t$_" } @{shift()}];
}
sub CreateRedditApi
{
    my $node = shift;
    my $text = "<?\n";
    my @text = ();

    push @text, '<?';
    push @text, 'class RedditApiBase';
    push @text, '{';
    push @text, @{AddTab(CreateApiAccessors($node->{api}))};
    push @text, @{AddTab(CreateMainCtor($node))};
    push @text, "}";
    foreach my $apiNode (@{$node->{api}})
    {
	push @text, @{CreateApi($apiNode)};
    }
    return join("\n", @text) . "\n";
}
sub CreateMainCtor
{
    my $node = shift;
    my @text = ();
    push @text, "public function RedditApiBase(\$endpointRetriever)";
    push @text, "{";
    push @text, @{AddTab(CreateMainCtorCode($node->{api}))};
    push @text, "}";
    \@text;
}
sub CreateMainCtorCode
{
    return [map { 
	my $varName = ApiNameToVarName($_->{name});
	my $className = ApiNameToClassName($_->{name});
	"\$this->$varName = new $className(\$endpointRetriever);";
    } @{shift()}];
}
sub CreateApiAccessors
{
    return [map { 'public $' . ApiNameToVarName($_->{name}) . ';' } @{shift()}];
}
sub CreateApi
{
    my $node = shift;
    my @text = ();
    my $className = ApiNameToClassName($node->{name});
    push @text, sprintf "class %s", $className;
    push @text, '{';
    push @text, @{AddTab(CreateApiCtor($className))};

    foreach (@{$node->{endpoint}})
    {
	push @text, @{AddTab(CreateEndpoint($_))};
    }
    push @text, "}";
    \@text;
}
sub CreateApiCtor
{
    my $className = shift;
    return [
    'protected $endpointRetriever;',
    "public function $className(\$endpointRetriever)",
    '{',
    '	$this->endpointRetriever = $endpointRetriever;',
    '}',
    ];
}
sub CreateEndpoint
{
    my $node = shift;
    my @text;
    push @text, CreateEndpointFunctionDefinition($node);
    push @text, "{";
    push @text, @{AddTab(CreateEndpointFunctionCode($node))};
    push @text, "}";
    return \@text;
}
sub CreateEndpointFunctionDefinition
{
    my $node = shift;
    my $text = sprintf "public function %s(", $node->{name};
    my @parameters = @{$node->{parameter}};

    my $parameterCount = 0;
    $text .= 
	join ", ", 
    	map { 
	    my $parameter = $parameters[$_->[1]];
	    my $rv = '$' . $parameter->{name};
	    $rv .= sprintf " = %s", ($parameter->{default} ? "'$parameter->{default}'" : null) if ($parameter->{optional} eq 'true');
	    $rv;
	}
    	sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } #make optional last, otherwise keep order
    	map { [$parameters[$_]->{optional} eq 'true', $_] } #make an array where 0: optional (bool), 1: index
	grep { $parameters[$_]->{static} ne 'true' } #no statics
	(0..$#parameters);
    $text .= ")";
    $text;
}
sub CreateEndpointFunctionCode
{
    my $node = shift;
    my @parameters = @{$node->{parameter}};
    my @code = ();
    my $method = (uc($node->{method}) eq 'POST' ? 'POST' : 'GET');
    push @code, "\$endpoint = new Endpoint('$method');";
    foreach (@parameters)
    {
	my $value = $_->{static} ? "'$_->{value}'" : '$' . $_->{name};
	my $format = $_->{format} ? "'$_->{format}'" : "null";
	if ($_->{querystring} eq 'true' or $_->{cookie} eq 'true') 
	{
	    my $name = $_->{name};
	    my $function = ($_->{querystring} eq 'true' ? "AddOption" : "AddCookie");
	    push @code, "\$endpoint->$function('$name', $value, $format);";
	}
	else 
	{
	    my $format = $_->{format} ? "'$_->{format}'" : "null";
	    push @code, "\$endpoint->addPart($value, $format);";
	}
    }
    push @code, 'return $this->endpointRetriever->retrieveEndpoint($endpoint);';
    return \@code;
}

